package i18n

import (
	"errors"
	"fmt"
	"strings"
	"unicode"
)

// Message 单条语言消息，支持普通消息和复数消息
//   - Text/Plural 字段对应两种消息类型，不能同时被设置，只能选择其一
//   - Text/Plural 字段不能同时为空
//   - Text 普通消息文本, 支持包含 fmt.Sprintf 格式化字符, 如 "Welcome %s!", 其中 %s 为参数占位符
//   - Text 为空时自动处理为"", 表示当且节点为一个分组或使用Plural
//   - Plural 复数消息文本, 至少要设置2个复数字段, 支持自定义Plural规则和其他更多复数表示的字符串字段
//   - Plural 为空时自动处理为nil, 表示当且节点为一个分组或使用Text
type Message struct {
	Text   string
	Plural *PluralMessage
}

// FlatMapMessages 扁平化消息数据类型(一维平铺的的消息数据), 用于存储和查找单个语言的所有消息文本
//   - key: 消息键名(已通过 ParseKey 校验的一级键名或多级组合键名)
//   - key: 如 "keyName","groupName.subName.keyName"
//   - value: *Message 消息
type FlatMapMessages map[string]*Message

// NewMessage 创建新的普通消息对象, 如果为空则返回错误
func NewMessage(text string) (*Message, error) {
	if text == "" || strings.TrimSpace(text) == "" {
		return nil, ErrParseMessageEmptyText
	}
	return &Message{Text: text}, nil
}

// MustMessage 创建新的普通消息对象, 如果为空则panic错误
func MustMessage(text string) *Message {
	if text == "" || strings.TrimSpace(text) == "" {
		panic("MustMessage: " + ErrParseMessageEmptyText.Error())
	}
	return &Message{Text: text}
}

// Display 格式化显示消息翻译文本
//   - 对于普通消息, 支持fmt格式化占位符, 如消息文本无自定义格式化占位符, 但T函数传递了多余的附加参数，则直接使用fmt.Sprint拼接参数到Text文本后输出
//   - 对于复数消息, 支持fmt格式化占位符, 如消息文本无自定义格式化占位符, 但T函数传递了多余的附加参数，则直接丢弃多余的参数
//   - 如果格式化失败，则返回空字符串
func (m *Message) Display(args ...any) string {
	if m == nil || (m.Text == "" && m.Plural == nil) {
		return ""
	}
	// 复数消息文本 PluralMessage.Display()
	if m.Plural != nil {
		return m.Plural.Display(args...)
	}
	// 简单消息文本直接格式化输出
	if len(args) == 0 {
		return m.Text
	}
	// 有自定义格式化占位符, 统计格式化参数个数是否匹配
	formatCount := getFormatArgsCount(m.Text)
	if formatCount > 0 {
		if len(args) > formatCount {
			// 参数过多，则需要剔除多余的参数，否则会导致格式化参数个数不匹配
			args = args[:formatCount]
		} else if len(args) < formatCount {
			// 占位符个数超出参数个数，则需要往args中的填充any{nil}参数，否则会导致格式化参数个数不匹配
			args = append(args, make([]any, formatCount-len(args))...)
		}
		return fmt.Sprintf(m.Text, args...)
	}
	// 对于普通消息, 如消息文本无自定义格式化占位符, 但是T函数传递了多余的附加参数，则直接使用fmt.Sprint拼接参数到Text文本后输出
	if len(args) > 0 {
		return m.Text + " " + fmt.Sprint(args...)
	}
	return m.Text
}
func (m *Message) Clone() *Message {
	cm := Message{Text: m.Text, Plural: nil}
	if m.Plural != nil {
		cm.Plural = m.Plural.Clone()
	}
	return &cm
}

var (
	ErrParseMessageNil           = errors.New("i18n.ParseMessage: value is nil")
	ErrParseMessageEmpty         = errors.New("i18n.ParseMessage: value is empty")
	ErrParseMessageTypesBeSet    = errors.New("i18n.ParseMessage: Both `value.Text` and `value.Plural` message types have been set, only one can be used")
	ErrParseMessageInvalidType   = errors.New("i18n.ParseMessage: invalid element type")
	ErrParseMessageInvalidText   = errors.New("i18n.ParseMessage: invalid `value.Text` element")
	ErrParseMessageInvalidPlural = errors.New("i18n.ParseMessage: invalid `value.Plural` element")
	ErrParseMessageInvalidGroup  = errors.New("i18n.ParseMessage: invalid `value.Group` element")
	ErrParseMessageEmptyText     = errors.New("i18n.ParseMessage: `value.Text` element is empty")
	ErrParseMessageEmptyPlural   = errors.New("i18n.ParseMessage: `value.Plural` element is empty")
	ErrParseMessageEmptyGroup    = errors.New("i18n.ParseMessage: `value.Group` element is empty")
	ErrParseKeyInvalid           = errors.New("i18n.ParseKey: invalid key")
	ErrParseKeyEmpty             = errors.New("i18n.ParseKey: key is empty")
)

// ParseKey 格式化消息键名, 统一为小驼峰命名规则(camelCase), 并校验是否为合法的键名
//   - 仅允许字母、字母+数字，以统一兼容语言包文件变量命名格式
//   - 任何非ASCII或不可打印字符都不允许出现在键名中
//   - 特殊约定：以一个下划线开始key为配置用途
//   - "_text","_plural","_group" 为系统保留配置键名, 不做为消息文本键名使用
//   - 如 "groupName.userName" => "GroupName.UserName"
//   - 如 "user_name" => "userName"
//   - 如 "user name " => "userName"
//   - 如 " user&name " => "userName"
//   - 如 "_lastName" => "_lastName"
func ParseKey(key string) (string, error) {
	if key == "" {
		return "", ErrParseKeyEmpty
	}
	var builder strings.Builder
	isStart, nextUpper, lastDot, lastLetter, lastDigit := true, false, false, false, false
	// 移除左右空白字符和右侧下划线后开始遍历字符串
	for _, c := range strings.Trim(strings.TrimRight(key, ". -_"), ". ") {
		switch {
		case c == '.':
			if builder.Len() == 0 || lastDot {
				continue
			}
			builder.WriteRune('.')
			isStart = false
			lastDot = true
			lastLetter = false
			lastDigit = false
			nextUpper = false
		case c == '_':
			if builder.Len() == 0 {
				builder.WriteRune('_')
				nextUpper = false
			} else if !isStart {
				nextUpper = true
			}
		case unicode.IsLetter(c):
			if nextUpper {
				builder.WriteRune(unicode.ToUpper(c))
				nextUpper = false
			} else if isStart || lastDot {
				builder.WriteRune(unicode.ToLower(c))
			} else {
				builder.WriteRune(c)
			}
			isStart = false
			lastDot = false
			lastLetter = true
			lastDigit = false
		case unicode.IsNumber(c):
			if !lastLetter && !lastDigit {
				// 数字上一个字符只能是字母或数字
				return "", ErrParseKeyInvalid
			}
			builder.WriteRune(c)
			isStart = false
			lastDot = false
			lastLetter = false
			lastDigit = true
		default:
			// 空白或其他符号字符当做连字符处理，下一个字母转为大写
			if builder.Len() > 0 {
				if unicode.IsSpace(c) || unicode.IsSymbol(c) || unicode.IsPunct(c) {
					if lastLetter {
						nextUpper = true
					}
				}
			}
		}
	}
	if builder.Len() == 0 {
		return "", ErrParseKeyInvalid
	}
	return builder.String(), nil
}

// ParseMessage 格式化及校验消息结构, 自动进行ParseKey处理, 并检查Text/Plural/Group字段有效性
//   - Text/Plural 字段对应两种消息类型，不能同时被设置，只能选择其一
func ParseMessage(msg *Message) error {
	if msg == nil {
		return ErrParseMessageNil
	}
	// Text/Plural 字段不能同时为空
	if msg.Text == "" && msg.Plural == nil {
		return ErrParseMessageEmpty
	}
	// Text/Plural 字段对应两种消息类型，不能同时被设置，只能选择其一
	if msg.Text != "" && msg.Plural != nil {
		return ErrParseMessageTypesBeSet
	}
	if msg.Plural != nil {
		msg.Text = ""
		if msg.Plural.Index < 1 {
			return errors.Join(errors.New("`value.Plural` index ininvalid or no set"), ErrParseMessageInvalidPlural)
		}
		if len(msg.Plural.Texts) < 1 {
			return errors.Join(errors.New("`value.Plural` text ininvalid or no set"), ErrParseMessageEmptyPlural)
		}
	} else {
		msg.Plural = nil
		if msg.Text == "" {
			return ErrParseMessageEmptyText
		}
	}
	return nil
}
